package dao

import models.Tables._
import org.joda.time.DateTime
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import play.api.libs.json.JsValue
import slick.jdbc.JdbcProfile
import tool.Pojo.PageData

import javax.inject.Inject
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

class BoxplotDataDao @Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends
  HasDatabaseConfigProvider[JdbcProfile] {


  import utils.MyPostgresProfile.api._

  type MyRow = BoxplotDataRow
  val table = BoxplotData

  def insert(row: MyRow) = {
    db.run(table += row)
  }

  def insertAll(rows: Seq[MyRow]) = db.run(table ++=
    rows)

  def deleteAll(): Future[Unit] = db.run(table.delete).map(_ => ())

  def getTrueColumnName(x: String) = {
    val r = "([A-Z])".r
    r.replaceAllIn(x, (matcher) =>
      s"_${matcher.group(1).toLowerCase}"
    )
  }

  def selectAll: Future[Seq[MyRow]] = db.run(table.sortBy(_.id.asc).result)

  def selectAll(geneName: String): Future[Seq[MyRow]] = db.run(table.filter(_.gene === geneName).result)

  def selectAll(geneName: String, phenotype: String) = db.run(table
    .filter(_.gene === geneName).filter(_.phenotype === phenotype).result)

  def selectAll(map: collection.Map[String, JsValue], pageData: PageData) = {
    val columnTrue = LiteralColumn(true)
    val searchJsons = map("search").as[List[JsValue]]
    val filterData = table.filter { x =>
      val textJsons = searchJsons.filter { j =>
        (j \ "kind").as[String] == "text"
      }
      val bs = textJsons.map { j =>
        val columnName = (j \ "name").as[String]
        val trueColumnName = getTrueColumnName(columnName)
        val searchType = (j \ "searchType").as[String]
        val v = (j \ "value").as[String]
        val vs = v.split(";")
        val cV = x.getStrColumn(trueColumnName)
        vs.map { eachV =>
          searchType match {
            case "like" => cV.like(s"%${eachV}%")
            case "equals" => cV === s"${eachV}"
          }
        }.reduceLeft(_ || _)
      }
      val b = bs.foldLeft(true.bind)((x, y) => x && y)
      b
    }.filter { x =>
      val checkboxJsons = searchJsons.filter { j =>
        (j \ "kind").as[String] == "checkbox"
      }
      val bs = checkboxJsons.map { j =>
        val columnName = (j \ "name").as[String]
        val trueColumnName = getTrueColumnName(columnName)
        val vs = (j \ "value").as[List[String]]
        val cV = x.value.getStrColumn(trueColumnName)
        cV.inSetBind(vs)
      }
      val b = bs.foldLeft(true.bind)((x, y) => x && y)
      b
    }.filter { x =>
      val numJsons = searchJsons.filter { j =>
        (j \ "kind").as[String] == "num"
      }
      val bs = numJsons.map { j =>
        val columnName = (j \ "name").as[String]
        val trueColumnName = getTrueColumnName(columnName)
        val minVOp = (j \ "min").asOpt[Double]
        val maxVOp = (j \ "max").asOpt[Double]
        val cV = x.value.getIntColumn(trueColumnName).asColumnOf[Double]
        val maxB = maxVOp.map { maxV =>
          cV <= maxV
        }.getOrElse(columnTrue)
        val minB = minVOp.map { minV =>
          cV >= minV
        }.getOrElse(columnTrue)
        minB && maxB
      }
      val b = bs.foldLeft(true.bind)((x, y) => x && y)
      b
    }.filter { x =>
      val numJsons = searchJsons.filter { j =>
        (j \ "kind").as[String] == "double"
      }
      val bs = numJsons.map { j =>
        val columnName = (j \ "name").as[String]
        val trueColumnName = getTrueColumnName(columnName)
        val minVOp = (j \ "min").asOpt[Double]
        println(minVOp)
        val maxVOp = (j \ "max").asOpt[Double]
        val cV = x.value.getDoubleColumn(trueColumnName)
        val maxB = maxVOp.map { maxV =>
          cV <= maxV
        }.getOrElse(columnTrue)
        val minB = minVOp.map { minV =>
          cV >= minV
        }.getOrElse(columnTrue)
        minB && maxB
      }
      val b = bs.foldLeft(true.bind)((x, y) => x && y)
      b
    }.filter { x =>
      val allSearchText = map("allSearch").as[String]
      val bs = table.baseTableRow.create_*.map { fs =>
        val columnName = fs.name
        fs.tpe.toString match {
          case v if v.startsWith("String") => x.getStrColumn(columnName).like(s"%${allSearchText}%")
          case v if v.startsWith("Int") => x.getIntColumn(columnName).asColumnOf[String].like(s"%${allSearchText}%")
          case v if v.startsWith("Double") => x.getDoubleColumn(columnName).asColumnOf[String].like(s"%${allSearchText}%")
        }
      }
      val b = bs.reduceLeft((x, y) => x || y)
      b
    }

    val sortData = map.get("sort").map { json =>
      val field = (json \ "field").as[String]
      val kind = (json \ "kind").as[String]
      val order = (json \ "order").as[String]
      val columnName = getTrueColumnName(field)
      kind match {
        case "int" => order match {
          case "asc" => filterData.sortBy { y =>
            y.getIntColumn(columnName)
          }
          case "desc" => filterData.sortBy { x =>
            x.getIntColumn(columnName).desc
          }
        }
        case "double" => order match {
          case "asc" => filterData.sortBy { y =>
            y.getDoubleColumn(columnName)
          }
          case "desc" => filterData.sortBy { x =>
            x.getDoubleColumn(columnName).desc
          }
        }
        case "str" =>
          order match {
            case "asc" => filterData.sortBy { y =>
              y.getStrColumn(columnName)
            }
            case "desc" => filterData.sortBy { x =>
              x.getStrColumn(columnName).desc
            }
          }
        case _ => filterData
      }
    }.getOrElse(filterData)

    val finalData = sortData
    val countF = finalData.length.result
    val resultF = finalData.drop(pageData.offset).take(pageData.limit).result
    val f = countF.zip(resultF)
    db.run(f)
  }

  def selectUniqueValues(keys: List[String]) = {
    val action = DBIO.sequence {
      val rs = keys.map { key =>
        val trueColumnName = getTrueColumnName(key)
        table.map { t =>
          t.getStrColumn(trueColumnName)
        }.distinct.result
      }
      rs
    }
    db.run(action)
  }

}
